#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time    : 2024/11/10 11:41
# @Author  : Tom_zc
# @FileName: auto_pr.py
# @Software: PyCharm

"""
脚本流程：
1. 从GitCode获取JSON数据
2. 克隆或更新Git仓库
3. 读取指定的Markdown文件
4. 查找并解析目标表格
5. 将JSON数据转换为表格格式
6. 更新或添加数据到表格中
7. 将更新后的表格写回Markdown文件
8. 提交并推送更改到Git仓库
"""

import os
import sys
import pandas as pd
import logging
from pathlib import Path
from tabulate import tabulate
import git
import time
from git import Repo, GitCommandError
from typing import List, Dict, Optional

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
)
logger = logging.getLogger(__name__)

# 将utils目录添加到系统路径
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
sys.path.append(os.path.join(BASE_DIR, "utils"))

from utils.gitcode_helper import GitCodeHelper


class JsonToTableConverter:
    """
    负责将从API获取的JSON数据转换为更新Markdown表格所需的标准格式。
    """

    def __init__(self):
        """
        初始化转换器，并硬编码列定义。
        """
        self.table_headers = ['no', 'feature', 'status', 'sig', 'owner']
        self.unique_column = 'no'

    def convert_json_to_standard_format(self, event: dict) -> dict:
        """将单个JSON对象转换为标准格式的字典。"""
        item_id = event.get('id', '')
        html_url = event.get('html_url', '')
        no_value = f"[{item_id}]({html_url})" if item_id and html_url else ""
        sig = next((label.get('name', '') for label in event.get('labels', []) if
                    str(label.get('name', '')).startswith('sig')), '')
        assignees = event.get('assignees', [])
        owner = assignees[0].get('login', '') if assignees else ''
        status = next(
            (item.get('value', '') for item in event.get('values', []) if item.get('field_name') == 'issue状态'), '')

        standard_data = {
            'no': no_value,
            'feature': event.get('title', ''),
            'status': status,
            'sig': sig,
            'owner': owner,
        }
        return standard_data

    def convert(self, json_data: List[Dict]) -> pd.DataFrame:
        if not isinstance(json_data, list):
            logger.warning("输入的JSON数据不是列表，将处理单个对象。")
            json_data = [json_data]

        standard_list = [self.convert_json_to_standard_format(item) for item in json_data]
        if not standard_list:
            return pd.DataFrame(columns=self.table_headers)
        return pd.DataFrame(standard_list)


class GitMarkdownTableUpdater:

    def __init__(self, repo_url, local_path, file_path, branch="main"):
        self.repo_url = repo_url
        self.local_path = Path(local_path)
        self.file_path = file_path
        self.branch = branch
        self.json_converter = JsonToTableConverter()
        self.repo = None

    def clone_or_pull_repo(self):
        """
        克隆或拉取最新的Git仓库。
        """
        try:
            if self.local_path.exists() and any(self.local_path.iterdir()):
                logger.info(f"仓库目录已存在: {self.local_path}，执行pull操作")
                self.repo = Repo(self.local_path)
                origin = self.repo.remote(name='origin')
                origin.pull(self.branch)
                logger.info("成功拉取最新代码")
            else:
                logger.info(f"克隆仓库到: {self.local_path}")
                self.repo = Repo.clone_from(self.repo_url, self.local_path, branch=self.branch)
                logger.info("成功克隆仓库")
        except GitCommandError as e:
            logger.error(f"Git操作失败: {e}", exc_info=True)
            raise

    def find_target_table(self, markdown_content: str) -> Optional[Dict]:
        """
        在Markdown内容中查找目标表格。
        使用模糊匹配，只要表头包含所有必需的列即可。
        """
        logger.info("开始在Markdown内容中查找目标表格...")
        target_headers = self.json_converter.table_headers
        lines = markdown_content.splitlines()

        for i, line in enumerate(lines):
            if line.strip().startswith('|') and line.strip().endswith('|'):
                line_headers = [h.strip() for h in line.strip().strip('|').split('|')]
                if all(header in line_headers for header in target_headers):
                    logger.info(f"在第 {i + 1} 行找到匹配的表头: '{line.strip()}'")

                    if i + 1 < len(lines) and all(c in '|-: ' for c in lines[i + 1].strip()):
                        start_line_index = i
                        end_line_index = i + 2
                        while end_line_index < len(lines) and lines[end_line_index].strip().startswith('|'):
                            end_line_index += 1

                        table_content = "\n".join(lines[start_line_index:end_line_index])
                        logger.info(f"成功提取表格内容，共 {end_line_index - start_line_index - 2} 行数据")
                        return {"content": table_content, "start": start_line_index, "end": end_line_index,
                                "headers": line_headers}

        logger.error(f"未找到任何匹配的表格。期望表头包含: {target_headers}")
        return None

    def markdown_table_to_dataframe(self, table_str: str, columns: List) -> pd.DataFrame:
        """
        将Markdown表格字符串手动解析为DataFrame，以处理复杂单元格。
        """
        lines = table_str.strip().split('\n')
        if len(lines) < 3:
            return pd.DataFrame(columns=columns)

        data = []
        # Skip header (lines[0]) and separator (lines[1])
        for line in lines[2:]:
            if not line.strip().startswith('|'):
                continue
            cells = [cell.strip() for cell in line.strip().strip('|').split('|')]
            if len(cells) == len(columns):
                data.append(dict(zip(columns, cells)))
            else:
                logger.warning(f"行数据与列数不匹配，已忽略: {line}")

        return pd.DataFrame(data)

    def dataframe_to_markdown_table(self, df: pd.DataFrame) -> str:
        """
        将DataFrame转换为格式良好的Markdown表格。
        """
        return tabulate(df, headers='keys', tablefmt='github', showindex=False, stralign='left') + '\n'

    def update_and_commit(self, raw_json_data: List[Dict], commit_message: str) -> str:
        try:
            md_file_abs_path = self.local_path / self.file_path
            if not md_file_abs_path.exists():
                logger.error(f"Markdown文件不存在: {md_file_abs_path}")
                return 'ERROR'

            markdown_content = md_file_abs_path.read_text(encoding='utf-8')
            target_table_info = self.find_target_table(markdown_content)
            if not target_table_info:
                return 'ERROR'

            table_columns = target_table_info['headers']

            df_new_data = self.json_converter.convert(raw_json_data)

            if df_new_data.empty:
                df_updated = pd.DataFrame(columns=table_columns)
            else:
                final_columns = [col for col in table_columns if col in df_new_data.columns]
                df_updated = df_new_data[final_columns]

            updated_table_str = self.dataframe_to_markdown_table(df_updated)

            lines = markdown_content.splitlines()
            start, end = target_table_info['start'], target_table_info['end']
            new_lines = lines[:start] + updated_table_str.strip().splitlines() + lines[end:]
            new_markdown_content = "\n".join(new_lines) + "\n"

            if new_markdown_content == markdown_content:
                logger.info("文件内容无实际变化，无需更新或提交。")
                return 'NO_CHANGES'

            md_file_abs_path.write_text(new_markdown_content, encoding='utf-8')
            logger.info("Markdown文件已更新")

            relative_md_path = md_file_abs_path.relative_to(self.local_path)

            # is_dirty() 和 git.add() 都需要使用相对路径
            if self.repo.is_dirty(path=str(relative_md_path)):
                logger.info(f"检测到文件变化: {relative_md_path}")
                self.repo.git.add(str(relative_md_path))
                self.repo.index.commit(commit_message)
                origin = self.repo.remote(name='origin')
                push_info = origin.push()
                if any(p.flags & git.remote.PushInfo.ERROR for p in push_info):
                    for p in push_info:
                        if p.flags & git.remote.PushInfo.ERROR:
                            logger.error(f"推送失败: {p.summary}")
                    return 'ERROR'
                logger.info("成功提交并推送到远程仓库")
                return 'COMMITTED'
            else:
                logger.info("文件无变化，无需提交")
                return 'NO_CHANGES'

        except Exception as e:
            logger.error(f"更新提交流程失败: {e}", exc_info=True)
            return 'ERROR'


def main():
    gitcode_user = os.getenv("gitcode_username")
    token = os.getenv("gitcode_token")
    domain = os.getenv("gitcode_api_domain")
    local_path = "repo_temp"
    file_path = os.getenv("file_path")
    branch = "dev"

    owner = os.getenv("gitcode_owner")
    repo_name = os.getenv("repo_name")
    repo_url = f"https://{gitcode_user}:{token}@gitcode.com/{owner}/{repo_name}.git"
    pr_number = os.getenv("issue_number")
    kanban_name = os.getenv("kanban_name")

    ALLOWED_STATUSES = ["已接纳", "开发中", "转测中", "已完成"]

    if not token or not kanban_name:
        logger.error("缺少环境变量 GITCODE_TOKEN 或 GITCODE_KANBAN_NAME")
        sys.exit(1)

    helper = GitCodeHelper(owner, repo_name, pr_number, token, domain)
    kanban_list = helper.get_kanban_list(kanban_name + " 需求看板")
    kanban_content = kanban_list.get('content')
    if not kanban_content or not isinstance(kanban_content, list):
        logger.error("API返回的看板字典中 'content' 键对应的值为空或不是列表。")
        sys.exit(1)

    kanban_id = kanban_content[0].get('id')
    raw_json_data = helper.get_kanban_item_list(kanban_id)
    logger.info(f"成功从GitCode看板 {kanban_id} 获取 {len(raw_json_data)} 条数据")

    original_count = len(raw_json_data)
    filtered_data = []
    for item in raw_json_data:
        status = next((v.get('value', '') for v in item.get('values', []) if v.get('field_name') == 'issue状态'), '')
        if status in ALLOWED_STATUSES:
            filtered_data.append(item)
    raw_json_data = filtered_data
    raw_json_data.sort(key=lambda x: int(x.get('id', 0)))
    logger.info(f"根据状态过滤数据：从 {original_count} 条减少到 {len(raw_json_data)} 条")

    if not raw_json_data:
        logger.warning("没有获取到任何JSON数据，流程终止")
        return

    is_new_branch = False
    branch_list = helper.get_branch()
    branch_names = [b.get('name') for b in branch_list]
    if branch not in branch_names:
        logger.info(f"分支 '{branch}' 不存在，正在从 'main' 创建...")
        try:
            time.sleep(60)
            helper.create_branch(branch)
            logger.info(f"成功创建分支 '{branch}'")
            is_new_branch = True
        except Exception as e:
            logger.error(f"创建分支 '{branch}' 失败: {e}", exc_info=True)
            return

    commit_message = f"feat(docs): auto-update {Path(file_path).name} from kanban"

    updater = GitMarkdownTableUpdater(repo_url, local_path, file_path, branch)
    updater.clone_or_pull_repo()
    commit_status = updater.update_and_commit(raw_json_data, commit_message)
    if commit_status == 'COMMITTED':
        if is_new_branch:
            logger.info(f"检测到新创建的分支 '{branch}'，正在尝试创建合并请求...")
            pr_data = {
                "title": f"feat(docs): 自动更新 {Path(file_path).name}",
                "head": branch,
                "base": "main",
                "body": "此合并请求由自动化脚本创建，用于同步看板中的最新数据。",
                "inner_issue_nums": f"#{owner}/{repo_name}#{pr_number}",
                "prune_source_branch": True
            }
            try:
                pr_response = helper.create_pr(pr_data)
                if pr_response and pr_response.get("iid"):
                    logger.info(f"成功创建合并请求！PR ID: {pr_response.get('iid')}, URL: {pr_response.get('web_url')}")
                else:
                    logger.error(f"创建合并请求失败: {pr_response}")
            except Exception as e:
                logger.error(f"创建合并请求时发生异常: {e}", exc_info=True)
    elif commit_status == 'NO_CHANGES':
        if is_new_branch:
            logger.info(f"检测到新创建的分支 '{branch}' 且无文件变化，正在删除该分支...")
            try:
                helper.delete_branch(branch)
                logger.info(f"成功删除分支 '{branch}'")
            except Exception as e:
                logger.error(f"删除分支 '{branch}' 失败: {e}", exc_info=True)
    else:
        logger.error("Git表格更新流程失败")


if __name__ == '__main__':
    main()